package desktop.core.playfield;

import pulpcore.Stage;
import pulpcore.animation.Timeline;
import pulpcore.animation.event.AddSpriteEvent;
import pulpcore.animation.event.RemoveSpriteEvent;
import pulpcore.scene.Scene2D;
import pulpcore.sprite.Group;
import desktop.conf.BarConf;
import desktop.conf.LevelConf;
import desktop.core.bar.Bar;
import desktop.core.bar.BarBuilder;
import desktop.core.bar.BarController;
import desktop.core.playfield.util.PlayFieldViewSquareDisappearEvent;
import desktop.core.playfield.util.Replay;
import desktop.core.square.Square;

public abstract class PlayFieldViewAbstract extends Group {
	protected Timeline timeline = null;
	protected Replay replay = new Replay();
	protected boolean replaying = false;

	protected PlayFieldModel playFieldModel;
	protected long timeToSpeedUp = 0;
	protected long timeToControlBar = 0;
	protected double speed = LevelConf.getSpeedInit();

	public PlayFieldViewAbstract(double x, double y, double width,
			double height, PlayFieldModel playFieldModel) {
		super(x, y, width, height);
		this.playFieldModel = playFieldModel;
	}

	public void addTimeline(Timeline timeline) {
		replay.addTimeline(timeline);
	}

	public void slowDown() {
		this.speed = this.speed * 2 / 3;
	}

	public int getLevel() {
		return LevelConf.getLevel(speed);
	}

	public int getPointsTotally() {
		return this.playFieldModel.getPointsTotal();
	}

	public int getPointsCurrentlly() {
		return this.playFieldModel.getPointsByOneElimination();
	}

	public void squareDisappearEvent(Square square) {
		remove(square);
		((Scene2D) Stage.getScene()).getMainLayer().add(square);
	}

	protected void controlSpeed(int elapsedTime) {
		this.timeToSpeedUp += elapsedTime;
		if (this.timeToSpeedUp >= LevelConf.getTimeUnit()) {
			this.speed += LevelConf.getSpeedUnit();
			this.timeToSpeedUp = 0;
		}
	}

	protected boolean reachesBottom(Bar bar) {
		return (bar.y.get() + bar.height.get()) >= this.getBottomY(bar);
	}

	protected void applySquaresFromBar(Bar bar) {
		int columnId = this.xToColumnId(bar.getXLeftTop());
		int rowId = this.yToRowId(bar.getYLeftTop());
		timeline = new Timeline();
		Timeline t = new Timeline();
		for (int i = 0; i < bar.getColumn(); i++) {
			for (int j = 0; j < bar.getRow(); j++) {
				Square square = bar.getSquare(i, j);

				double oldX = square.x.get();
				double oldY = square.y.get();
				double newX = this.columnIdToX(columnId + i);
				double newY = this.rowIdToY(rowId + j);

				timeline.animate(square.x, oldX, newX, 0);
				timeline.animate(square.y, oldY, newY, 0);
				timeline.addEvent(new AddSpriteEvent(this, square, 0));

				t.animate(square.x, oldX, newX, 0);
				t.animate(square.y, oldY, newY, 0);
				t.addEvent(new AddSpriteEvent(this, square, 0));

				playFieldModel.setSquare(columnId + i, rowId + j, square);
			}
		}
		((Scene2D) Stage.getScene()).addTimeline(timeline);
		replay.addTimeline(t);
	}

	protected int applySquareElimination() {
		final int DURATION = 200;
		int delay = 0;

		this.applyGravity(delay, DURATION);
		playFieldModel.applyGravity();
		delay += DURATION;

		while (playFieldModel.eliminationExists()) {
			this.eliminateSimply(delay, DURATION);
			playFieldModel.eliminateSimply();
			delay += DURATION;

			this.applyGravity(delay, DURATION);
			playFieldModel.applyGravity();
			delay += DURATION;
		}

		return delay;
	}

	protected void applyGravity(int delay, int DURATION) {
		timeline = new Timeline(delay);
		Timeline t = new Timeline(delay);
		for (int i = 0; i < this.playFieldModel.getColumn(); i++) {

			double destinationY = this.height.get() - this.getHeightUnit();
			for (int j = this.playFieldModel.getRow() - 1; j >= 0; j--) {

				Square square = (Square) this.playFieldModel.getSquare(i, j);
				if (square != null) {
					double oldY = rowIdToY(j);

					timeline.animate(square.y, oldY, destinationY, DURATION);

					t.animate(square.y, oldY, destinationY, DURATION);

					destinationY -= this.getHeightUnit();
				}

			}
		}
		((Scene2D) Stage.getScene()).addTimeline(timeline);
		replay.addTimeline(t);
	}

	protected void eliminateSimply(int delay, int DURATION) {
		timeline = new Timeline(delay);
		Timeline t = new Timeline(delay);
		for (int i = 0; i < this.playFieldModel.getColumn(); i++) {
			for (int j = 0; j < this.playFieldModel.getRow(); j++) {
				if (playFieldModel.isEiminatable(i, j)) {
					Square square = (Square) this.playFieldModel
							.getSquare(i, j);

					double dx = this.x.get();
					double oldX = dx + columnIdToX(i);
					double newX = dx + columnIdToX(i) - square.width.get()
							* 0.25;
					double oldY = rowIdToY(j);
					double newY = rowIdToY(j) - square.height.get() * 0.25;
					double oldW = square.width.get();
					double newW = square.width.get() * 1.5;
					double oldH = square.height.get();
					double newH = square.height.get() * 1.5;

					timeline.addEvent(new PlayFieldViewSquareDisappearEvent(0,
							this, square));
					timeline.animate(square.x, oldX, newX, DURATION);
					timeline.animate(square.y, oldY, newY, DURATION);
					timeline.animate(square.width, oldW, newW, DURATION);
					timeline.animate(square.height, oldH, newH, DURATION);
					timeline.animateTo(square.alpha, 0, DURATION);
					timeline.addEvent(new RemoveSpriteEvent(((Scene2D) Stage
							.getScene()).getMainLayer(), square, DURATION));

					t.addEvent(new PlayFieldViewSquareDisappearEvent(0, this,
							square));
					t.animate(square.x, oldX, newX, DURATION);
					t.animate(square.y, oldY, newY, DURATION);
					t.animate(square.width, oldW, newW, DURATION);
					t.animate(square.height, oldH, newH, DURATION);
					t.animateTo(square.alpha, 0, DURATION);
					t.addEvent(new RemoveSpriteEvent(((Scene2D) Stage
							.getScene()).getMainLayer(), square, DURATION));

				}
			}
		}
		((Scene2D) Stage.getScene()).addTimeline(timeline);
		replay.addTimeline(t);
	}

	protected Bar receiveNextBar(int columnId) {
		int column = BarConf.getColumn();
		int row = BarConf.getRow();
		return BarBuilder.getBuilder().produceBar(this.columnIdToX(columnId),
				this.rowIdToY(-row), this.getWidthUnit(), this.getHeightUnit(),
				this.speed, column, row);
	}

	protected void controlCurrentBar(Bar currentBar,
			BarController barController, int elapsedTime) {
		if (timeToControlBar <= 0) {
			timeToControlBar = 0;
			double yBottom = this.getBottomY(currentBar)
					- currentBar.height.get();
			barController.controlBar(this, currentBar, canMoveLeft(currentBar),
					canMoveRight(currentBar), yBottom, elapsedTime);
		} else {
			timeToControlBar -= elapsedTime;
		}

	}

	abstract protected boolean canMoveLeft(Bar bar);

	abstract protected boolean canMoveRight(Bar bar);

	protected boolean isLeftEdge(Bar bar) {
		return 0 == this.xToColumnId(bar.getXLeftTop());
	}

	protected boolean isLeftIntersecs(Bar bar) {
		for (int i = 0; i < this.playFieldModel.getColumn(); i++) {
			for (int j = 0; j < this.playFieldModel.getRow(); j++) {
				Square square = (Square) this.playFieldModel.getSquare(i, j);
				if (square != null) {
					double xCheck, yCheck;
					xCheck = bar.getXLeftTop() - square.width.get() / 2;
					yCheck = bar.getYLeftTop();
					if (square.containsPoint(xCheck, yCheck)) {
						return true;
					}
					xCheck = bar.getXLeftBottom() - square.width.get() / 2;
					yCheck = bar.getYLeftBottom();
					if (square.containsPoint(xCheck, yCheck)) {
						return true;
					}
				}
			}
		}
		return false;
	}

	protected boolean isRightEdge(Bar bar) {
		return (this.playFieldModel.getColumn() - 1) == this.xToColumnId(bar
				.getXRightTop()
				- this.getWidthUnit());
	}

	protected boolean isRightIntersecs(Bar bar) {
		for (int i = 0; i < this.playFieldModel.getColumn(); i++) {
			for (int j = 0; j < this.playFieldModel.getRow(); j++) {
				Square square = (Square) this.playFieldModel.getSquare(i, j);
				if (square != null) {
					double xCheck, yCheck;
					xCheck = bar.getXRightTop() + square.width.get() / 2;
					yCheck = bar.getYRightTop();
					if (square.containsPoint(xCheck, yCheck)) {
						return true;
					}
					xCheck = bar.getXRightBottom() + square.width.get() / 2;
					yCheck = bar.getYRightBottom();
					if (square.containsPoint(xCheck, yCheck)) {
						return true;
					}
				}
			}
		}
		return false;
	}

	/** 返回空白区域下边缘 */
	protected double getBottomY(Bar bar) {
		double bottom = this.height.get();
		for (int i = 0; i < bar.getColumn(); i++) {
			int columnId = this.xToColumnId(bar.x.get() + this.getWidthUnit()
					* i);
			if (bottom > this.getBottomY(columnId)) {
				bottom = this.getBottomY(columnId);
			}
		}
		return bottom;
	}

	private double getBottomY(int columnId) {
		return this.playFieldModel.getCurrentBottomRowId(columnId)
				* this.getHeightUnit();
	}

	/**
	 * 四舍五入可能有问题
	 */
	private int xToColumnId(double x) {
		return (int) Math.round(x / this.getWidthUnit());
	}

	private int yToRowId(double y) {
		return (int) Math.round(y / this.getHeightUnit());
	}

	protected double columnIdToX(int columnId) {
		return this.getWidthUnit() * columnId;
	}

	protected double rowIdToY(int rowId) {
		return this.getHeightUnit() * rowId;
	}

	protected double getWidthUnit() {
		return this.width.get() / this.playFieldModel.getColumn();
	}

	protected double getHeightUnit() {
		return this.height.get() / this.playFieldModel.getRow();
	}

}